// This program demonstrates double buffering for flicker-free animation.
// It spins a multi-colorable square on a black background, with
// variable speed from keyboard input.

#ifdef __APPLE_CC__
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif
#include <cstdlib>
#include <ctime>

// Integer definitions for the keys
static const unsigned char KEY_c = 99;
static const unsigned char KEY_C = 67;
static const unsigned char KEY_PLUS = 43;
static const unsigned char KEY_MINUS = 95;

// Set this to true to animate.
static bool spinning = true;

// This is the number of frames per second to render.
static const int FPS = 60;

// The speed modifier for response to button presses
static float speedMod = 1.0;
static const float SPEED_INCREMENT = 0.09;

// The random color to make the square
static GLfloat randColor[] = {1, 1, 1};

// This global variable keeps track of the current orientation of the square.
// It is better to maintain the "state" of the animation globally rather
// than trying to successively accumulate transformations.  Over a period of
// time the approach of accumulating transformation matrices generally
// degrades due to rounding errors.
static GLfloat currentAngleOfRotation = 0.0;

// Handles the window reshape event by first ensuring that the viewport fills
// the entire drawing surface.  Then we use a simple orthographic projection
// with a logical coordinate system ranging from -50..50 in the smaller of
// the width or height, and scale the larger dimension to make the whole
// window isotropic.
void reshape(GLint w, GLint h) {
    glViewport(0, 0, w, h);
    GLfloat aspect = (GLfloat)w / (GLfloat)h;
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (w <= h) {
        // width is smaller, go from -50 .. 50 in width
        glOrtho(-50.0, 50.0, -50.0/aspect, 50.0/aspect, -1.0, 1.0);
    } else {
        // height is smaller, go from -50 .. 50 in height
        glOrtho(-50.0*aspect, 50.0*aspect, -50.0, 50.0, -1.0, 1.0);
    }
}

// Handles the display callback as follows: first clears the window, then draws
// a 50 x 50 rectangle centered at the origin and rotated the correct number
// of degrees around the vector <0,0,1>.  This function ends with a
// 'glutSwapBuffers' call because when the display mode is double buffered,
// drawing takes place on the back buffer; we have to call glutSwapBuffers()
// to show what we have drawn.
void display() {
    glClear(GL_COLOR_BUFFER_BIT);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glRotatef(currentAngleOfRotation, 0.0, 0.0, 1.0);
    glColor3fv(randColor);
    glRectf(-25.0, -25.0, 25.0, 25.0);
    glFlush();
    glutSwapBuffers();
}

// Handles the timer by incrementing the angle of rotation and requesting the
// window to display again, provided the program is in the spinning state.
// Since the timer function is only called once, it sets the same function to
// be called again.
void timer(int v) {
    if (spinning) {
        currentAngleOfRotation += 1.0;
        if (currentAngleOfRotation > 360.0) {
            currentAngleOfRotation -= 360.0;
        }
        glutPostRedisplay();
    }
    glutTimerFunc((1000/FPS) * speedMod, timer, v);
}

// Handles mouse events as follows: when the left button is pressed, generate
// new animation frames while the application is idle; when the right button
// is pressed, remove any idle-time callbacks, thus stopping the animation.
void mouse(int button, int state, int x, int y) {
    if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) {
        spinning = true;
    } else if (button == GLUT_RIGHT_BUTTON && state == GLUT_DOWN) {
        spinning = false;
    }
}

// Colors the square a random color that is not too close to black
void recolor() {
    float colorCheck = 0;
    for(int i = 0; i < 3; i++) {
        randColor[i] = (float) rand()/RAND_MAX;
        colorCheck += randColor[i];
    }

    // If the random color was too close to black, call the function again
    if (colorCheck < 0.05) {
        recolor();
    }
}

// Handles the keyboard inputs to slow, speed, or color the spinning square
void keyboard(unsigned char key, int, int) {
    switch (key) {
        case KEY_c:
        case KEY_C:
            recolor();
            break;
        case KEY_MINUS:
            speedMod += SPEED_INCREMENT;
            break;
        case KEY_PLUS:
            speedMod -= SPEED_INCREMENT;
            if (speedMod <= 0) speedMod = 0.01;
            break;
    }
}

// Initializes GLUT, the display mode, and main window; registers callbacks;
// enters the main event loop.
int main(int argc, char** argv) {
    srand(time(NULL));
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
    glutInitWindowPosition(80, 80);
    glutInitWindowSize(800, 500);
    glutCreateWindow("Spinning Square");
    glutReshapeFunc(reshape);
    glutDisplayFunc(display);
    glutTimerFunc(100, timer, 0);
    glutMouseFunc(mouse);
    glutKeyboardFunc(keyboard);
    glutMainLoop();
}
